require 'rubygems'
require 'tk'
['config', 'colors', 'connection', 'script'].each {|s| require "tk_mud_#{s}"}

class RubyMudApp
  attr_accessor :parent_pane, :main_pane, :add_pane
  attr_accessor :type_box, :show_box, :hist_box, :add_boxes
  attr_accessor :max_lines
  attr_reader :connection, :root
  
  def setup
    @root = TkRoot.new
    @max_lines = 5000
    @parent_pane = TkPanedWindow.new(@root) {
      height 700
      orient 'horizontal'
      pack('fill' => 'both', 'expand' => true)
    }
    @main_pane = TkPanedWindow.new(@parent_pane) {
      width 650
      orient 'vertical'
      pack('fill' => 'both', 'expand' => true)
    }
    @add_pane = TkPanedWindow.new(@parent_pane) {
      width 400
      orient 'vertical'
      pack('fill' => 'both', 'expand' => true)
    }
    @show_box = TkText.new(@main_pane) {
      height 40
      font 'TkFixedFont'
      background '#000000'
      foreground '#B4B4B4'
      wrap 'word'
      pack('fill' => 'both', 'expand' => true)
    }
    @hist_box = TkText.new(@main_pane) {
      height 20
      font 'TkFixedFont'
      background '#000000'
      foreground '#B4B4B4'
      wrap 'word'
      pack('fill' => 'both')
    }
    @type_box = TkText.new(@main_pane) {
      height 2
      font 'TkFixedFont'
      background '#000000'
      foreground '#FFFFFF'
      wrap 'word'
      insertbackground '#FFFFFF'
      pack('fill' => 'both')
    }
    @main_pane.add(@show_box, 'minsize' => 24)
    @main_pane.add(@type_box, 'minsize' => 24)
    @main_pane.add(@hist_box, 'before' => @main_pane.panes[0], 'minsize' => 24)
    @main_pane.forget(@hist_box)
    @parent_pane.add(@main_pane)
    @parent_pane.add(@add_pane)
    
    @add_boxes = {}
    
    @type_box.bind('Return') {|e| handle_return;break}
    @type_box.bind('KP_Enter') {|e| handle_return;break}
    @type_box.bind('Prior') {
      unless @main_pane.panes.include?(@hist_box)
        @hist_box.insert('end', @show_box.get(1.0, 'end'))
        @hist_box.yview('end')
        x, y = @main_pane.sash_coord(0)
        @main_pane.add(@hist_box, 'before' => @main_pane.panes[0], 'minsize' => 24)
        @main_pane.sash_place(1, x, y)
        @show_box.yview('end')
      else
        @hist_box.focus
        @hist_box.event_generate('Prior')
        @type_box.focus
      end
      break
    }
    @type_box.bind('Next') {
      if @main_pane.panes.include?(@hist_box)
        @hist_box.focus
        @hist_box.event_generate('Next')
        @type_box.focus
      end
      break
    }
    @hist_box.bind('Next') {
      if @main_pane.panes.include?(@hist_box) && @hist_box.yview.include?(1.0)
        x, y = @main_pane.sash_coord(1)
        @main_pane.forget(@hist_box)
        @main_pane.sash_place(0, x, y)
        @hist_box.delete('0.0', 'end')
      end
    }
    @type_box.bind('Key') {|e| break unless KeyHook.run(e)}
    @show_box.bind('Key') {|e| @type_box.focus;@type_box.event_generate(e.keysym);break}

    @type_box.focus
    @color_handler_hist = RubyMudColors.new(@hist_box)
    @color_handler = RubyMudColors.new(@show_box)
  end
  def mainloop; @root.mainloop; end
  
  def setup_connection
    @connection = RubyMudConnection.new($world_info['host'], $world_info['port'])
    @connection.add_observer(self)
    TkTimer.new(10, -1) {@connection.run}.start
    
    @message_list = Array.new
    TkTimer.new(5, -1) {
      unless @message_list.empty?
        send = @message_list.dup
        @message_list = []
        self.show_text_handler(send);
      end
    }.start#.slice!(0)) until @message_list.empty?}.start
    
    @send_list = Array.new
    TkTimer.new(5, -1) {
      unless @send_list.empty?
        msg = @send_list.slice!(0)
        if msg.start_with?('SENDINVIS|')
          msg = msg.split('|', 2)[1]
        end
        @connection.send_text(msg)
      end
    }.start
    
    TkTimer.new(10000, -1) {
      numlines = @show_box.index("end - 1 line").split('.')[0].to_i
      @show_box.delete(1.0, numlines.to_f - @max_lines) if numlines > @max_lines
    }.start
    
    @cmd_list = LimitedArray.new
    @cmd_list.set_size(20)
    @cmd = 0
  end
  
  def add_box(name, watch, hold = false, timestamp = false, clear = false)
    return if @add_boxes.key?(name)
    @add_boxes[name] = AddBox.new(@add_pane, name, watch, hold, timestamp, clear)
    @add_pane.add(@add_boxes[name].text)
  end
  def delete_box(name)
    
  end
  def set_add_box_get(name, proc)
    @add_boxes[name].get_proc = proc
  end
  
  def handle_return
    text = @type_box.get('0.0', 'end')
    self.add_cmd(text.chomp)
    self.show_text_handler("#{text}\n")
    new_text = UpstreamHook.run(text.chomp)
    @connection.send_text(new_text) unless new_text.nil?
    @type_box.delete('0.0', 'end')
  end
  
  def show_text(text, scroll = true)
    if text =~ /^\[([\d;]+?)m$/
      $1.split(';').each {|style| @color_handler.set_tags(style)}
    else
      #@hist_box.insert('end', text, @color_handler.get_tags)
      #@hist_box.yview('end') unless @main_pane.panes.include?(@hist_box)
      @show_box.insert('end', text, @color_handler.get_tags)
      @show_box.yview('end') if scroll
    end
  end
  def show_text_handler(*text_list)
    text_list.flatten!
    text_list.each {|text|
      unless text.empty?
        text_list = text.gsub(/(\x1B)(\[[\d;]+?m)/, '\1\2\1').split(/\x1B/)
        text_list.each {|t| self.show_text(t, false)}
      end
    }
    @show_box.yview('end')
  end
  def show_new_text(text)
    @add_boxes.each_value {|b| text = b.grab_text(text)}
    text = DownstreamHook.run(text)
    unless text.nil? || text.empty?
      RubyMudScript.new_downstream(text.clean_colors)
      @message_list << text
    end
    #self.show_text_handler(text)
  end
  def update(text)
    text = GMCP.notify(text) if defined?(GMCP) and GMCP.class == Class
    return if text.nil? || text.empty?
    text.split("\n").each {|t|
      self.show_new_text(t.gsub(/[^\x20-\x7E|\x0A\x1B]+/, '') + "\n")
    }
  end
  
  def send_text(text, invis=false)
    @send_list << "#{invis ? 'SENDINVIS|' : ''}#{text}"
  end
  def add_cmd(cmd); @cmd = @cmd_list.push(@cmd_list.delete(cmd) {cmd}).size unless cmd.length <= 1 end
  def show_previous_cmd
    @cmd = @cmd <= 0 ? @cmd_list.size : @cmd - 1
    @type_box.delete('0.0', 'end')
    @type_box.insert('end', @cmd == @cmd_list.size ? '' : @cmd_list[@cmd])
  end
  def show_next_cmd
    @cmd = @cmd >= @cmd_list.size ? 0 : @cmd + 1
    @type_box.delete('0.0', 'end')
    @type_box.insert('end', @cmd == @cmd_list.size ? '' : @cmd_list[@cmd])
  end
  def set_type_box_text(cmd)
    @type_box.delete('0.0', 'end')
    @type_box.insert('end', cmd)
  end

  def show_message(text)
    if main_thread? then show_text_handler(text) else @message_list << text end
  end
end

class AddBox
  attr_accessor :name, :watch, :hold, :get_proc, :timestamp, :clear
  attr_reader :text
  
  def initialize(parent, name, watch, hold = false, timestamp = true, clear = false)
    @name, @watch, @hold, @timestamp, @clear, @get_proc = name, watch, hold, timestamp, clear, nil
    @text = TkText.new(parent) {
      font 'TkFixedFont'
      background '#000000'
      foreground '#B4B4B4'
      wrap 'word'
      pack('fill' => 'both')
    }
    @color_handler = RubyMudColors.new(@text)
  end
  
  def display_text(t, ts_override = nil)
    disp = (ts_override.nil? ? @timestamp : ts_override) ? "#{Time.now.strftime("[%H:%M:%S]")} #{t}" : t
    text_list = disp.gsub(/(\x1B)(\[[\d;]+m)/, '\1\2\1').split(/\x1B/)
    @text.delete('0.0', 'end') if @clear
    text_list.each {|text|
      if text =~ /^\[([\d;]+)m$/
        $1.split(';').each {|style| @color_handler.set_tags(style)}
      else
        @text.insert('end', text, @color_handler.get_tags)
        @text.yview('end')
      end
    }
  end
  def grab_text(ss)
    return ss if ss.clean_colors !~ @watch
    show = @get_proc.nil? ? ss : @get_proc.call(ss)
    self.display_text(show) unless show.empty?
    @hold ? '' : ss
  end
end

$app = RubyMudApp.new
exit unless ARGV[0]
$app.setup
add_world(ARGV[0])
$app.setup_connection
$app.mainloop
